@Import注解:导入配置类的四种方式&源码解析 您所在的位置:网站首页 @importresource 多个配置文件 @Import注解:导入配置类的四种方式&源码解析

@Import注解:导入配置类的四种方式&源码解析

2023-11-07 21:26| 来源: 网络整理| 查看: 265

微信搜索:码农StayUp 主页地址:https://gozhuyinglong.github.io 源码分享:https://github.com/gozhuyinglong/blog-demos

平时喜欢看源码的小伙伴,应该知道Spring中大量使用了@Import注解。该注解是Spring用来导入配置类的,等价于Spring XML中的元素。

本文将对该注解进行介绍,并通过实例演示它导入配置类的四种方式,最后对该注解进行源码解析。

话不多说,走起~

简介

@Import注解的全类名是org.springframework.context.annotation.Import。其只有一个默认的value属性,该属性类型为Class[],表示可以传入一个或多个Class对象。

通过注释可以看出,该注解有如下作用:

可以导入一个或多个组件类(通常是@Configuration配置类)该注解的功能与Spring XML中的元素相同。可以导入@Configuration配置类、ImportSelect和ImportBeanDefinitionRegistrar的实现类。从4.2版本开始,还可以引用常规组件类(普通类),该功能类似于AnnotationConfigApplicationContext.register方法。该注解可以在类中声明,也可以在元注解中声明。如果需要导入XML或其他非@Configuration定义的资源,可以使用@ImportResource注释。 导入配置类的四种方式

源码注释写得很清楚,该注解有四种导入方式:

普通类@Configuration配置类ImportSelector的实现类ImportBeanDefinitionRegistrar的实现类

下面我们逐个来介绍~

准备工作

创建四个配置类:ConfigA、ConfigB、ConfigC、ConfigD。其中ConfigB中增加@Configuration注解,表示为配置类,其余三个均为普通类。

ConfigA:

public class ConfigA { public void print() { System.out.println("输出:ConfigA.class"); } }

ConfigB:

@Configuration public class ConfigB { public void print() { System.out.println("输出:ConfigB.class"); } }

ConfigC:

public class ConfigC { public void print() { System.out.println("输出:ConfigC.class"); } }

ConfigD:

public class ConfigD { public void print() { System.out.println("输出:ConfigD.class"); } }

再创建一个主配置类Config,并试图通过@Resource注解将上面四个配置类进行注入。当然,这样是不成功的,还需要将它们进行导入。

@Configuration public class Config { @Resource ConfigA configA; @Resource ConfigB configB; @Resource ConfigC configC; @Resource ConfigD configD; public void print() { configA.print(); configB.print(); configC.print(); configD.print(); } } 方式一:导入普通类

导入普通类非常简单,只需在@Import传入类的Class对象即可。

@Configuration @Import(ConfigA.class) public class Config { ... } 方式二:导入@Configuration配置类

导入配置类与导入普通类一样,在@Import注解中传入目标类的Class对象。

@Configuration @Import({ConfigA.class, ConfigB.class}) public class Config { ... } 方式三:导入ImportSelector的实现类

ImportSelector接口的全类名为org.springframework.context.annotationImportSelector。其主要作用的是收集需要导入的配置类,并根据条件来确定哪些配置类需要被导入。

该接口的实现类同时还可以实现以下任意一个Aware接口,它们各自的方法将在selectImport之前被调用:

EnvironmentAwareBeanFactoryAwareBeanClassLoaderAwareResourceLoaderAware

另外,该接口实现类可以提供一个或多个具有以下形参类型的构造函数:

EnvironmentBeanFactoryClassLoaderResourceLoader

如果你想要推迟导入配置类,直到处理完所有的@Configuration。那么你可以使用DeferredImportSelector

下面我们创建一个实现该接口的类 MyImportSelector。

看下面示例:

在selectImports方法中,入参AnnotationMetadata为主配置类 Config 的注解元数据。 返回值为目标配置类 ConfigC 的全类名,这里是一个数组,表示可以导入多个配置类。

public class MyImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[]{"io.github.gozhuyinglong.importanalysis.config.ConfigC"}; } }

在配置类 Config 中导入 MyImportSelector 类。

@Configuration @Import({ConfigA.class, ConfigB.class, MyImportSelector.class}) public class Config { ... } 方式四:导入ImportBeanDefinitionRegistrar的实现类

该接口的目的是有选择性的进行注册Bean,注册时可以指定Bean名称,并且可以定义bean的级别。其他功能与ImportSelector类似,这里就不再赘述。

下面来看示例:

创建一个实现 ImportBeanDefinitionRegistrar 接口的类 MyImportBeanDefinitionRegistrar,并在 registerBeanDefinitions方法中注册 configD 类。 入参 AnnotationMetadata为主配置类 Config 的注解元数据;BeanDefinitionRegistry参数可以注册Bean的定义信息。

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { registry.registerBeanDefinition("configD", new RootBeanDefinition(ConfigD.class)); } }

在配置类 Config 中导入 MyImportBeanDefinitionRegistrar 类。

@Configuration @Import({ConfigA.class, ConfigB.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class}) public class Config { ... } 测试结果

创建一个测试类 ImportDemo,看上面四个配置类是否被注入。

public class ImportDemo { public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class); Config config = ctx.getBean(Config.class); config.print(); } }

输出结果:

输出:ConfigA.class 输出:ConfigB.class 输出:ConfigC.class 输出:ConfigD.class

通过输出结果可以看出,这四个配置类被导入到主配置类中,并成功注入。

源码解析

ConfigurationClassParser类为Spring的工具类,主要用于分析配置类,并产生一组ConfigurationClass对象(因为一个配置类中可能会通过@Import注解来导入其它配置类)。也就是说,其会递归的处理所有配置类。

doProcessConfigurationClass

其中的doProcessConfigurationClass方法是处理所有配置类的过程,其按下面步骤来处理:

@Component注解@PropertySource注解@ComponentScan注解@Import注解@ImportResource注解@Bean注解配置类的接口上的默认方法配置类的超类 @Nullable protected final SourceClass doProcessConfigurationClass( ConfigurationClass configClass, SourceClass sourceClass, Predicate filter) throws IOException { if (configClass.getMetadata().isAnnotated(Component.class.getName())) { // 1.首先会递归的处理所有成员类,即@Component注解 processMemberClasses(configClass, sourceClass, filter); } // 2.处理所有@PropertySource注解 for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) { if (this.environment instanceof ConfigurableEnvironment) { processPropertySource(propertySource); } else { logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment"); } } // 3.处理所有@ComponentScan注解 Set componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { for (AnnotationAttributes componentScan : componentScans) { // 配置类的注解为@ComponentScan-> 立即执行扫描 Set scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); // 检查扫描过的BeanDefinition集合,看看是否有其他配置类,如果需要,递归解析 for (BeanDefinitionHolder holder : scannedBeanDefinitions) { BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition(); if (bdCand == null) { bdCand = holder.getBeanDefinition(); } if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { parse(bdCand.getBeanClassName(), holder.getBeanName()); } } } } // 4.处理所有@Import注解 processImports(configClass, sourceClass, getImports(sourceClass), filter, true); // 5.处理所有@ImportResource注解 AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class); if (importResource != null) { String[] resources = importResource.getStringArray("locations"); Class readerClass = importResource.getClass("reader"); for (String resource : resources) { String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); configClass.addImportedResource(resolvedResource, readerClass); } } // 6.处理标注为@Bean注解的方法 Set beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } // 7.处理配置类的接口上的默认方法 processInterfaces(configClass, sourceClass); // 8.处理配置类的超类(如果有的话) if (sourceClass.getMetadata().hasSuperClass()) { String superclass = sourceClass.getMetadata().getSuperClassName(); if (superclass != null && !superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) { this.knownSuperclasses.put(superclass, configClass); // Superclass found, return its annotation metadata and recurse return sourceClass.getSuperClass(); } } // 处理完成 return null; } processImports

processImports方法为处理@Import注解导入的配置类,是我们本篇的主题。

该方法会循环处理每一个由@Import导入的类:

ImportSelector类的处理ImportBeanDefinitionRegistrar类的处理其它类统一按照@Configuration类来处理,所以加不加@Configuration注解都能被导入 /** * 处理配置类上的@Import注解引入的类 * * @param configClass 配置类,这里是Config类 * @param currentSourceClass 当前资源类 * @param importCandidates 该配置类中的@Import注解导入的候选类列表 * @param exclusionFilter 排除过滤器 * @param checkForCircularImports 是否循环检查导入 */ private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection importCandidates, Predicate exclusionFilter, boolean checkForCircularImports) { // 如果该@Import注解导入的列表为空,直接返回 if (importCandidates.isEmpty()) { return; } // 循环检查导入 if (checkForCircularImports && isChainedImportOnStack(configClass)) { this.problemReporter.error(new CircularImportProblem(configClass, this.importStack)); } else { this.importStack.push(configClass); try { // 循环处理每一个由@Import导入的类 for (SourceClass candidate : importCandidates) { if (candidate.isAssignable(ImportSelector.class)) { // 1. ImportSelector类的处理 Class candidateClass = candidate.loadClass(); ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class, this.environment, this.resourceLoader, this.registry); Predicate selectorFilter = selector.getExclusionFilter(); if (selectorFilter != null) { exclusionFilter = exclusionFilter.or(selectorFilter); } if (selector instanceof DeferredImportSelector) { // 1.1 若是DeferredImportSelector接口的实现,则延时处理 this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector); } else { // 1.2 在这里调用我们的ImportSelector实现类的selectImports方法 String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata()); Collection importSourceClasses = asSourceClasses(importClassNames, exclusionFilter); // 1.3 递归处理每一个selectImports方法返回的配置类 processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false); } } else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) { // 2. ImportBeanDefinitionRegistrar类的处理 Class candidateClass = candidate.loadClass(); ImportBeanDefinitionRegistrar registrar = ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class, this.environment, this.resourceLoader, this.registry); configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata()); } else { // 3. 其它类统一按照@Configuration类来处理,所以加不加@Configuration注解都能被导入 this.importStack.registerImport( currentSourceClass.getMetadata(), candidate.getMetadata().getClassName()); processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter); } } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to process import candidates for configuration class [" + configClass.getMetadata().getClassName() + "]", ex); } finally { this.importStack.pop(); } } } 总结

通过上面源码的解析可以看出,@Import注解主要作用是导入外部类的,并且普通类也会按照@Configuration类来处理。这大大方便了我们将自己的组件类注入到容器中了(无需修改自己的组件类)。

源码分享

完整代码请访问我的Github,若对你有帮助,欢迎给个⭐,感谢~~🌹🌹🌹

https://github.com/gozhuyinglong/blog-demos/tree/main/spring-source-analysis/src/main/java/io/github/gozhuyinglong/importanalysis

推荐阅读 Java反射机制:跟着代码学反射JDK动态代理:不仅要学会用,更要掌握其原理 关于作者 项目内容公众号码农StayUp(ID:AcmenStayUp)主页https://gozhuyinglong.github.ioCSDNhttps://blog.csdn.net/gozhuyinglong掘进https://juejin.cn/user/1239904849494856Githubhttps://github.com/gozhuyinglongGiteehttps://gitee.com/gozhuyinglong



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有